//  Omnima Limited Copyright (c) 2011
//
//  Targets STM32L and STM32F family MCUs as used 
//  on the omnima-stm32-expander board
//
//  STM32L-Discovery GCC Demo program
//
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License
//  as published by the Free Software Foundation; either version 2
//  of the License, or (at your option) any later version.
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#include "stm32l1xx.h"
#include "sprintf.h"

#define DEBUG
#include "trace.h"

#define UART0_RX_BUFFER_SIZE 512

void STM_COMInit(USART_InitTypeDef* USART_InitStruct)
{  
  NVIC_InitTypeDef NVIC_InitStructure;
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

  // Enable the USARTx Interrupt
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);  
  
  GPIO_InitTypeDef GPIO_InitStructure;

  /* Enable GPIO clock */
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOA, ENABLE);

  /* Enable UART clock */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

  /* Connect PXx to USARTx_Tx */
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);

  /* Connect PXx to USARTx_Rx */
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
  
  /* Configure USART Tx as alternate function push-pull */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_40MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
    
  /* Configure USART Rx as alternate function push-pull */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
  GPIO_Init(GPIOA, &GPIO_InitStructure);

  /* USART configuration */
  USART_Init(USART1, USART_InitStruct);
      
  /* Enable USART */
  USART_Cmd(USART1, ENABLE);
  
  //USART_ITConfig(USART1, USART_IT_TXE, ENABLE);
  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}

void UART_Init()
{
  	USART_InitTypeDef USART_InitStructure;
	/* USART1 and USART2 configuration ------------------------------------------------------*/
	/* USART and USART2 configured as follow:
	 - BaudRate = 19200 baud
	 - Word Length = 8 Bits
	 - One Stop Bit
	 - No parity
	 - Hardware flow control disabled (RTS and CTS signals)
	 - Receive and transmit enabled
	*/
	USART_InitStructure.USART_BaudRate = 115200;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
		
	STM_COMInit(&USART_InitStructure);	
}

uint8_t  uart0_rx_buffer[UART0_RX_BUFFER_SIZE];
uint16_t uart0_rx_insert_idx, uart0_rx_extract_idx;

void USART1_ResetBuf()
{
    uart0_rx_insert_idx=0;
    uart0_rx_extract_idx=0;
}

uint8_t USART1_Insertch(uint8_t ch)
{  
      uint16_t temp;

      // calc next insert index & store character
      temp = (uart0_rx_insert_idx + 1) % UART0_RX_BUFFER_SIZE;

      // check for more room in queue
      if (temp != uart0_rx_extract_idx)
      {
	  uart0_rx_buffer[uart0_rx_insert_idx] = ch;
	  uart0_rx_insert_idx = temp; // update insert index
	  return 1;
      } else
      {
	  return 0;
      }
}

uint8_t USART1_ChAvailable() 
{
  return uart0_rx_insert_idx != uart0_rx_extract_idx;
}

uint8_t USART1_Getch()
{  
  uint8_t  ret = uart0_rx_buffer[uart0_rx_extract_idx];
  uart0_rx_extract_idx = (uart0_rx_extract_idx + 1)%UART0_RX_BUFFER_SIZE;
 
  return ret;
}

const uint8_t *cmdLeadStr=(uint8_t *)"#@";
uint8_t cmdParsePos=0;
uint8_t cmdReady;
uint8_t cmdChecksum;
uint16_t cmdLen;

#define cmdPrologueLen 2
#define cmdCmdLenByte1 2
#define cmdCmdLenByte2 3

#define SERIAL_ACK	0x79
#define SERIAL_ACKRET	0xAA
#define SERIAL_NACK	0x1F

void cmdSendAck(uint8_t checksumValue)
{
    USART_SendData(USART1, SERIAL_ACK);   //ACK  byte
    USART_SendData(USART1, checksumValue);   //checksumValue
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET)	{ ; };
}

void cmdSendAckRet(uint16_t ackValue)
{
    USART_SendData(USART1, SERIAL_ACKRET); //ACKRET byte
    USART_SendData(USART1, ackValue);      //Value
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET)	{ ; };    
}

void cmdSendNack(uint8_t expectedChecksum, uint8_t calcChecksum)
{
    USART_SendData(USART1, SERIAL_NACK);       //NACK  byte
    USART_SendData(USART1, expectedChecksum);  //Expected checksum
    USART_SendData(USART1, calcChecksum);   //Calculated checksum
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET)	{ ; };
}

uint8_t cmdIsReady()
{
    return cmdReady;
}

uint16_t cmdGetLength()
{
    return cmdLen;
}

uint8_t* cmdGetBuffer()
{
    return &uart0_rx_buffer[0];
}

void cmdProcessReset()
{
    cmdParsePos=0;
    cmdReady=0;
    cmdChecksum=0;
    cmdLen=0xffff;
    USART1_ResetBuf();
}

void cmdProcess(uint8_t ch)
{
    uint8_t incPos=0;
    
    //TRACE3("Received character: %c(0x%x) pos: %d\n", ch, ch, cmdParsePos);
    
    if (cmdReady)
    {
	//TRACE("A command has already been received, can't accept another one\n");
	//cmdSendNack(0, 0);
	return;
    }
    
    //Reset counters if processing is stating at offset 0
    if (cmdParsePos==0)
    {
        //TRACE("Starting to process new command\n");
	cmdProcessReset();
    }

    //Command prologue
    if (cmdParsePos<cmdPrologueLen)
    {
	if (ch==cmdLeadStr[cmdParsePos])
	{
	    //TRACE2("Prologue byte %d:%c - ok\n", cmdParsePos, ch);
	    incPos=1;
	} else
	{
	    //TRACE1("Prologue byte %d:%c - incorrect\n", ch);
	    cmdParsePos=0;
	}
    }
    //Command length byte 1
    if (cmdParsePos==cmdCmdLenByte1) 
    {
        //TRACE1("Length byte1=%x\n", ch);
	cmdLen=ch*256;
	incPos=1;
    }
    //Command length byte 2
    if (cmdParsePos==cmdCmdLenByte2) 
    {
        //TRACE1("Length byte2=%x\n", ch);
	cmdLen+=ch;
	incPos=1;
    }  
    //Command bytes
    if (cmdParsePos>cmdCmdLenByte2 && cmdParsePos<(cmdLen+4))
    {
        cmdChecksum^=ch;
	if (USART1_Insertch(ch))
	{
	    TRACE(".");
	    incPos=1;
	} else
	{
	    //TRACE("RX buffer overflow\n");
	    cmdParsePos=0;
	}
    }
    //Checksum byte
    if (cmdParsePos==(cmdLen+4))
    {
        //TRACE1("Command checksum byte received: %x\n", ch);
	if (ch==cmdChecksum)
	{
	    //TRACE("Command received successfully, dispatch it\n");
	    cmdReady=1;
	    cmdSendAck(ch);
	} else
	{
	    //TRACE2("Failed checksum check %x!=%x, send NACK back to sender\n", ch, cmdChecksum);
	    cmdSendNack(ch, cmdChecksum);
	    cmdParsePos=0;
	}
    }
    if (incPos) cmdParsePos++;
}

void USART1_IRQHandler(void)
{ 
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
	{
	    cmdProcess(USART_ReceiveData(USART1));
	}

	//Example for sending data via UART IRQ
	//if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET)
	//{  
	//   Write one byte to the transmit data register
	//   USART_SendData(USART1, TxBuffer);
	//}
}

int UART_SendChar(char ch, ULONG ud)
{
	ud; //Unused

	//Don't send 0's, the terminal output doesn't look neat if 0's are sent
	if (ch==0) return 1;

	USART_SendData(USART1, ch);

	/* Loop until USART1 DR register is empty */
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET)	{ ; }

	return 1;
}

void UART_SendData(void*buf,UINT32 len)
{
	f_putb(UART_SendChar, buf, len, 0);
}

void UART_SendString(const char*str)
{
	f_puts(UART_SendChar, str, 0);
}

void UART_printf(const char*str, ...)
{
	va_list arp;

	va_start(arp, str);
	funcprintfArp(UART_SendChar, 0, str, arp);
	va_end(arp);
}
